Skip to content

http2: avoid uaf while receiving and sending rst_stream#64166

Open
Eusgor wants to merge 1 commit into
nodejs:mainfrom
Eusgor:main
Open

http2: avoid uaf while receiving and sending rst_stream#64166
Eusgor wants to merge 1 commit into
nodejs:mainfrom
Eusgor:main

Conversation

@Eusgor

@Eusgor Eusgor commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Mark the session as receiving around nghttp2_session_mem_recv() and defer RST_STREAM handling while receive is in progress. This prevents closing a stream while nghttp2 still processes it and avoids heap-use-after-free in nghttp2_session_mem_recv2().

Fixes: #64113

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/http2
  • @nodejs/net

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. http2 Issues or PRs related to the http2 subsystem. needs-ci PRs that need a full CI run. labels Jun 27, 2026
@mertcanaltin

mertcanaltin commented Jun 27, 2026

Copy link
Copy Markdown
Member

Can you use "-s" (for Signed-off-by) in first commit command, after run this command
example: git commit --amend --no-edit -s "http2: avoid uaf while receiving and sending rst_stream"

after:
CLANG_FORMAT_START=$(git merge-base HEAD main) make format-cpp for cpp lint errors

Mark the session as receiving around nghttp2_session_mem_recv() and
defer RST_STREAM handling while receive is in progress. This prevents
closing a stream while nghttp2 still processes it and avoids
heap-use-after-free in nghttp2_session_mem_recv2().

Fixes: nodejs#64113
Signed-off-by: Evgeniy Gorbanev <gorbanev.es@gmail.com>
@Eusgor

Eusgor commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Is everything correct now?

@RafaelGSS RafaelGSS left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test case or an script that we could reproduce it?

@Eusgor

Eusgor commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Can you add a test case or an script that we could reproduce it?

The scripts are in the issue #64113
The error is reproduced when using the ASAN sanitizer.

@mcollina mcollina left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Comment thread src/node_http2.cc
// Do not call `nghttp2_session_mem_send()` while nghttp2 is processing
// incoming data. Sending may close the stream and free nghttp2 state
// that is still in use by `nghttp2_session_mem_recv()`.
if (session_->is_receiving() && available_outbound_length_ == 0) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think available_outbound_length_ == 0 still leaves the exact same UAF issue for the pending-writes case. E.g. if you write any data during a receive callback and then reset, this check would be false, and we'll miss this fix but hit the old UAF behaviour regardless.

I haven't tested this but looks very plausible, let me know if I've missed something.

If you move & invert (>0) the check into the inner if (so we always reset here in all receive cases, but we defer the reset for both cancel codes and pending-writes) then that'd cover this case as well.

@Eusgor

Eusgor commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

If you mean replace the code with this

  if (session_->is_receiving()) {
    if (is_stream_cancel(code) || available_outbound_length_ > 0) {
      session_->AddPendingRstStream(id_);
      return;
    }
    FlushRstStream();
    return;
  }

then it fixes uaf, but I get these failed tests:
test-http2-compat-errors
test-http2-destroy-after-write
test-http2-server-errors

Or if I misunderstood you, please write your version.

@pimterry

pimterry commented Jul 2, 2026

Copy link
Copy Markdown
Member

Ah, interesting, yes we'll have to investigate that @Eusgor.

I took a bit more of a look, I think this is part of a bigger more fundamental issue - the nghttp2 docs say:

Do not call nghttp2_session_send(), nghttp2_session_mem_send2(), nghttp2_session_recv() or nghttp2_session_mem_recv2() from the nghttp2 callback functions directly or indirectly. It will lead to the crash.

And in this method, we call SendPendingData while receiving.

The tests are reasonable (e.g. destroy after write - it should flush the written data before the stream is destroyed) but this method we're changing isn't - it should never call SendPendingData with is_receiving() === true.

I had Claude do a quick scan, there's at least three places we ignore this nghttp2 requirement:

  • Http2Stream::SubmitRstStream() - fixed by this PR, but only for the case where no writes are pending.
  • Http2Session::OnDataChunkReceived - can call SendPendingData if buffer is too large.
  • Http2Session::Close() - calls SendPendingData directly

All of those are issues (good find!) and the new is_receiving() flag you've added is the right way to fix all of them, but we should take a more general approach instead of fixing this small part of one case.

Can you take a look? Really I think we need to always defer anything anything that could call one of those methods while receiving - that means something like early-return in SendPendingData() if receiving, deferring resets if there is pending data, and deferred destroy/close to fix the test failures around this logic. Take a look, let me know if you need any pointers and I can come up with a more detailed outline if that'd help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Issues and PRs that require attention from people who are familiar with C++. http2 Issues or PRs related to the http2 subsystem. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use-after-free error in http2 server

6 participants